<?php
// +----------------------------------------------------------------------
// | ZengCMS [ 火火 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2018 http://zengcms.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: 火火 <zengcms@qq.com>
// +----------------------------------------------------------------------

// +----------------------------------------------------------------------
// | JwtAuth扩展类
// +----------------------------------------------------------------------
namespace app\api\lib;

use Lcobucci\JWT\Parser;
use Lcobucci\JWT\Builder;
use app\api\lib\OpensslAES;
use Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\ValidationData;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use app\api\lib\exception\MissException;
include_once APP_PATH . '/api/lib/jwt/vendor/autoload.php';
/**
 * jwt类引入JWT，TOKEN三个部分组成，头部、有效负荷、签名
 * 
 * 单例 一次请求中所有出现使用jwt的地方都是一个用户
 * 获取jwt的token：JwtAuth::getInstance()->setUid(1)->encode()->getToken(); // 已放弃使用
 * 
 * 获取jwt的token：JwtAuth::getInstance()->setDataFromString($string = '')->encode()->getToken(); // 设置用户数据data(字符串即数组转json并OpensslAES加密的字符串)
 * 获取jwt的token：JwtAuth::getInstance()->setDataFromArr($data = [])->encode()->getToken(); // 设置用户数据data(数组)
 * 
 * 验证jwt的token
 * $jwt = JwtAuth::getInstance()->setToken('xxxx')->decode();
 * $res = $jwt->validate(); // 检查token前两部分即头部header和负荷playload
 * $res = $jwt->verify();   // 检查token最后一部分即signature签名
 * 
 * 获取jwt的token中的信息
 * $data = $jwt->getData(); // 获取用户数据(即用户数组数据转json并OpensslAES加密的数据)
 * $data = $jwt->getDataStringByKey($key = ''); // 获取用户数据(字符串)
 * $data = $jwt->getDataArrByKey($key = []);    // 获取用户数据(数组)
 */
class JwtAuth
{
    // 单例模式，Jwt句柄
    private static $instance = null;
    // Jwt token
    private $token;
    // jwt token有效期，单位秒
    private $exp = 120;
    // decode token
    private $decodeToken;
    // 用户id数据
    // private $uid;
    // 用户数组数据转json并加密的数据
    private $data;
    // claim iss签发者
    private $iss = 'api.zengcms.com';
    // claim aud
    private $aud = 'zengcms_server.app';
    // claim jti
    private $jti = '4f1g23a12aa';
    // secrect
    private $secrect = 's3439758479$^%#$%#&%&**';
    /**
     * 获取Jwt的句柄，统一的入口
     * @return [type] [description]
     */
    public static function getInstance()
    {
        if (is_null(self::$instance)) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    /**
     * 私有化构造函数
     */
    private function __construct()
    {
    }
    /**
     * 私有化clone函数
     * @return [type] [description]
     */
    private function __clone()
    {
    }
    /**
     * 设置token
     * @param [type] $token [description]
     */
    public function setToken($token)
    {
        $this->token = $token;
        // 链式调用
        return $this; 
    }
    /**
     * 设置uid
     * @param [type] $uid [description]
     */
    /* public function setUid($uid)
    {
        $this->uid = $uid;
        // 链式调用
        return $this;
    } */
    /**
     * 设置用户数据data(字符串即数组转json并OpensslAES加密的字符串)
     * @param [type] $string [description]
     */
    public function setDataFromString($string = '')
    {
        if (empty($string)) {
            throw new MissException([
                // 异常消息内容
                'message' => 'Token中数据不能为空！',
                // http状态码
                'httpCode' => 400
            ]);
        }
        // 对数据string进行OpensslAES解密
        $str = (new OpensslAES())->decrypt($string);
        // 判断数据是否合法
        if (empty($str)) {
            throw new MissException([
                // 异常消息内容
                'message' => 'refresh_token数据不合法！',
                // http状态码
                'httpCode' => 401
            ]);
        }
        $arr = json_decode($str, true);
        if ($arr['ip'] != get_reluser_ip()) {
            throw new MissException([
                // 异常消息内容
                'message' => 'refresh_token异常！',
                // http状态码
                'httpCode' => 401
            ]);
        }
        if (time() - $arr['time'] > 30 * 24 * 60 * 60) {
            throw new MissException([
                // 异常消息内容
                'message' => 'refresh_token异常！',
                // http状态码
                'httpCode' => 401
            ]);
        }
        $arr['time'] = time();
        $string = (new OpensslAES())->encrypt(json_encode($arr));
        $this->data = $string;
        return $this;
    }
    /**
     * 设置用户数据data(数组)
     * @param [type] $data [description]
     */
    public function setDataFromArr($data = [])
    {
        if (empty($data)) {
            throw new MissException([
                // 异常消息内容
                'message' => 'Token中数据不能为空！',
                // http状态码
                'httpCode' => 400
            ]);
        }
        $this->data = (new OpensslAES())->encrypt(json_encode($data));
        return $this;
    }
    /**
     * 获取token
     * @return [type] [description]
     */
    public function getToken()
    {
        return (string) $this->token;
    }
    /**
     * 获取用户id
     * @return [type] [description]
     */
    /* public function getUid()
    {
        return (int)$this->uid;
    } */
    /**
     * 获取用户数据-即用户数组数据转json并加密的数据
     * @return [type] [description]
     */
    public function getData()
    {
        return $this->data;
    }
    /**
     * 获取用户数据(字符串)
     * @return [type] [description]
     */
    public function getDataStringByKey($key = '')
    {
        if (empty($key)) {
            throw new MissException([
                // 异常消息内容
                'message' => 'key不能为空！',
                // http状态码
                'httpCode' => 400
            ]);
        }
        $data = (new OpensslAES())->decrypt($this->data);
        if (empty($data)) {
            throw new MissException([
                // 异常消息内容
                'message' => 'token中数据不合法！',
                // http状态码
                'httpCode' => 400
            ]);
        }
        $data = json_decode($data, true);
        if (isset($data[$key])) {
            return $data[$key];
        } else {
            throw new MissException([
                // 异常消息内容
                'message' => '未找到key对应的值！',
                // http状态码
                'httpCode' => 400
            ]);
        }
    }
    /**
     * 获取用户数据(数组)
     * @return [type] [description]
     */
    public function getDataArrByKey($key = [])
    {
        $data = (new OpensslAES())->decrypt($this->data);
        if (empty($data)) {
            throw new MissException([
                // 异常消息内容
                'message' => 'token不合法！',
                // http状态码
                'httpCode' => 400
            ]);
        }
        $data = json_decode($data, true);
        if (empty($key)) {
            return $data;
        }
        $result = [];
        foreach ($key as $k) {
            if (array_key_exists($k, $data)) {
                $result[$k] = $data[$k];
            }
        }
        if (count($result) !== count($key)) {
            throw new MissException([
                'message' => '尝试获取的token中数据个别不存在！',
            ]);
        }
        return $result;
    }
    /**
     * 编码Jwt的token
     * @return [$this] [description]
     */
    public function encode()
    {
        // 实例化Sha256
        $signer = new Sha256();
        // 当前时间戳
        $time = time();
        $this->token = (new Builder())->issuedBy($this->iss) //配置颁发者（iss声明）
        ->permittedFor($this->aud) //配置访问群体（aud声明）
        ->identifiedBy($this->jti, true) //配置id（jti声明），复制为头项
        ->issuedAt($time) //配置颁发令牌的时间（iat声明）
        ->canOnlyBeUsedAfter($time + 0) //配置令牌可以使用的开始时间（nbf声明）
        ->expiresAt($time + $this->exp) //配置令牌的过期时间（exp claim）
        // ->withClaim('uid', $this->uid) //数据uid
        ->withClaim('data', $this->data) //配置令牌中数据
        ->getToken($signer, new Key($this->secrect)); //检索生成的令牌
        return $this;
    }
    /**
     * 解码Jwt的token
     * @return [type] [description]
     */
    public function decode()
    {
        if (!$this->decodeToken) {
            // Parsing from strings-从字符串分析
            $this->decodeToken = (new Parser())->parse((string) $this->token);
            // $this->uid = $this->decodeToken->getClaim('uid');
            $this->data = $this->decodeToken->getClaim('data');
        }
        return $this;
    }
    /**
     * validate校验token合法性
     * 检查token前两部分即头部header和负荷playload
     * @return [type] [description]
     */
    public function validate()
    {
        $time = time();
        $data = new ValidationData(); // It will use the current time to validate (iat, nbf and exp)-它将使用当前时间进行验证（iat、nbf和exp）
        $data->setIssuer($this->iss);
        $data->setAudience($this->aud);
        $data->setId($this->jti);
        $data->setCurrentTime($time + 0); // changing the validation time to future - 将验证时间更改为将来
        return $this->decodeToken->validate($data); // true, because current time is between "nbf" and "exp" claims
    }
    /**
     * verify校验token合法性
     * 检查token最后一部分即signature签名
     * @return [type] [description]
     */
    public function verify()
    {
        $result = $this->decodeToken->verify(new Sha256(), $this->secrect);
        return $result;
    }
}
